/*
 * Readaheadmanager.h
 *
 *  Created on: 01.02.2013
 *      Author: mario
 */

#ifndef READAHEADMANAGER_H_
#define READAHEADMANAGER_H_

// system
#include <vector>
#include <stdexcept>
#include <boost/thread.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>

// omnibus
#include "FileInfo.h"
#include "exceptions/not_found_exception.h"

// ariba
#include "ariba/Node.h"   // TODO make shared buffers easier/directly includable


using namespace std;
using boost::shared_ptr;

//#define FILE_QUEUE_SIZE     2
#define FILE_QUEUE_SIZE     200
#define CHUNKSIZE           4096


// forward declaration
class Filetransfer;


//** some exceptions **//
class Notification: public exception {};

class Buffer_Empty_Notification: public Notification
{
    virtual const char* what() const throw()
    {
        return "Buffer is empty.";
    }
};
//const Buffer_Empty_Notification buffer_empty_exception;


class End_of_File_Notification: public Notification
{
    virtual const char* what() const throw()
    {
        return "End of file reached.";
    }
}; // end_of_file_exception;

class Wait_for_it_Notification: public Notification
{
    virtual const char* what() const throw()
    {
        return "Data not ready yet.";
    }
}; // wait_for_it_exception;


class Bad_File_Exception: public exception
{
    virtual const char* what() const throw()
    {
        return "This file has errors.";
    }
}; // bad_file;

//** [some exceptions] **//


////////////////////////////////////////////////////////////////////////


// chunk of a file
struct file_chunk
{
    file_chunk(size_t chunk_size) :
        data(chunk_size)
    {
    }
    
    uint64_t position;
    uint64_t size;
    reboost::shared_buffer_t data;
};



class Read_File  :  public boost::noncopyable
{
    friend class read_ahead_thread;
    
public:
    enum State
    {
        INVALID     = 0,
        NEW         = 1,
        WAITING     = 2,
        READING     = 3,
        FINISHED    = 4,
        CANCEL      = 5,
        CANCELED    = 6,   // unused
        ERROR       = 7
    };
    
    Read_File(FilePtr f, uint64_t start_pos);
    ~Read_File();                            // Thread: make sure we don't need any locking here!
    
    
    size_t get_buffer_size();                // Thread: both  [buffer_lock]
    size_t append_chunk(file_chunk* data);    // Thread: both  [buffer_lock]

    // the caller has to delete the returned object
    file_chunk* get_next_chunk();            // Thread: both  [buffer_lock]
    
    void set_state(State state);             // Thread: both  [state_lock]
    State get_state();                        // Thread: both  [state_lock]
    
    void activate_callback();               // Thread: both  [callback_lock]
    void deactivate_callback();             // Thread: both  [callback_lock]

    
public:
    FilePtr fileptr;
    
private:
    string filename;                    // Thread: read_ahead_thread, (ariba, before added to FileList)
    uint64_t start_position;            // Thread: read_ahead_thread, (ariba, before added to FileList)

    State state;                        // Thread: both
    boost::mutex state_lock;            // LOCK
    
    ifstream file;                      // Thread: read_ahead_thread
    
    deque<file_chunk*> data_buffer;      // Thread: both  [buffer_lock]
    boost::mutex buffer_lock;           // LOCK
    
    bool callback_enabled;              // Thread: both  [callback_lock]
    boost::mutex callback_lock;          // LOCK
};




////////////////////////////////////////////////////////////////////////

//** Readahead_manager **//
class Readahead_manager :
    public boost::enable_shared_from_this<Readahead_manager>,
    public boost::noncopyable
{
    friend class read_ahead_thread;
        
public:
    typedef list<Read_File*> FileList;
    
    Readahead_manager() throw();
    virtual ~Readahead_manager() throw();
    
    /* starts / stops the manager thread
     *   -- intended to be used just once --
     */
    void start(Filetransfer* filetransfer) throw();
    void stop() throw();
    
    // adds / removes a new file to be read from
    void add_file(FilePtr file, uint64_t position) throw();
    void remove_file(FilePtr file) throw(not_found_exception);
    
    file_chunk get_next_chunk(FilePtr file) throw(
            End_of_File_Notification, 
            Wait_for_it_Notification,
            not_found_exception,
            Bad_File_Exception);
    
private:
    inline void _get_next_chunk_from(FileList& file_list, FilePtr file, Read_File*& f, file_chunk*& chunk)
        throw(Bad_File_Exception, Buffer_Empty_Notification);
    
    /** 
     * removes file from file_list
     * @return the removed read_file pointer (maybe you want to call delete on it) 
     */
    inline Read_File* _remove_file_from(FileList& file_list, FilePtr file);
    
    void trigger_callback(FilePtr file) const;        // Thread: to be called from read_ahead_thread

private:
    boost::thread* thread;
    // shutting down the read_ahead_thread
    bool stop_thread;                       // Thread: ariba (write once), read_ahead_thread (read-only)
    bool thread_is_running;                 // Thread: ariba (read-only while thread is running),
                                             //   read_ahead_thread (write once on exit)

    
    FileList finished_files2;               // Thread: ariba
    
    FileList files;                         // Thread: both  [filelist_mutex]
    bool new_work;                          // Thread: both  [filelist_mutex]
    bool thread_is_sleeping;                // Thread: ariba (read-only), read_ahead_thread (write)
    boost::mutex filelist_mutex;

    FileList finished_files1;               // Thread: both  [finished1_mutex]
    boost::mutex finished1_mutex;           // LOCK: deadlock prevention: filelist_mutex -> finished1_mutex
    
    FileList cancel_list;                   // Thread: ariba (append); read_ahead_thread (read, clear)
    boost::mutex cancel_list_mutex;         // LOCK: deadlock prevention: filelist_mutex -> cancel_list_mutex 
    
    // controls sleep & wake of read_ahead_thread
    boost::condition_variable work_condvar;
    
    
    
    // for callbacks
    Filetransfer* filetransfer;             // read-only while read_ahead_thread is running
};
//** [Readahead_manager] **//

////////////////////////////////////////////////////////////////////////

    
#endif /* READAHEADMANAGER_H_ */
